//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.zaicheng.cloud.remotecall.template;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class Template {
    private static final Logger logger = Logger.getLogger(Template.class.getName());
    private static final Pattern QUERY_STRING_PATTERN = Pattern.compile("(?<!\\{)(\\?)");
    private final String template;
    private final boolean allowUnresolved;
    private final Template.EncodingOptions encode;
    private final boolean encodeSlash;
    private final Charset charset;
    private final List<TemplateChunk> templateChunks = new ArrayList();

    Template(String value, Template.ExpansionOptions allowUnresolved, Template.EncodingOptions encode, boolean encodeSlash, Charset charset) {
        if (value == null) {
            throw new IllegalArgumentException("template is required.");
        } else {
            this.template = value;
            this.allowUnresolved = Template.ExpansionOptions.ALLOW_UNRESOLVED == allowUnresolved;
            this.encode = encode;
            this.encodeSlash = encodeSlash;
            this.charset = charset;
            this.parseTemplate();
        }
    }

    Template(Template.ExpansionOptions allowUnresolved, Template.EncodingOptions encode, boolean encodeSlash, Charset charset, List<TemplateChunk> chunks) {
        this.templateChunks.addAll(chunks);
        this.allowUnresolved = Template.ExpansionOptions.ALLOW_UNRESOLVED == allowUnresolved;
        this.encode = encode;
        this.encodeSlash = encodeSlash;
        this.charset = charset;
        this.template = this.toString();
    }

    public String expand(Map<String, ?> variables) {
        if (variables == null) {
            throw new IllegalArgumentException("variable map is required.");
        } else {
            StringBuilder resolved = null;
            Iterator var3 = this.templateChunks.iterator();

            while(var3.hasNext()) {
                TemplateChunk chunk = (TemplateChunk)var3.next();
                String expanded;
                if (chunk instanceof Expression) {
                    expanded = this.resolveExpression((Expression)chunk, variables);
                } else {
                    expanded = chunk.getValue();
                }

                if (expanded != null) {
                    if (resolved == null) {
                        resolved = new StringBuilder();
                    }

                    resolved.append(expanded);
                }
            }

            if (resolved == null) {
                return null;
            } else {
                return resolved.toString();
            }
        }
    }

    protected String resolveExpression(Expression expression, Map<String, ?> variables) {
        String resolved = null;
        Object value = variables.get(expression.getName());
        if (value != null) {
            String expanded = expression.expand(value, this.encode.isEncodingRequired());
            if (expanded != null) {
                if (!this.encodeSlash) {
                    logger.fine("Explicit slash decoding specified, decoding all slashes in uri");
                    expanded = expanded.replaceAll("%2F", "/");
                }

                resolved = expanded;
            }
        } else if (this.allowUnresolved) {
            resolved = this.encodeLiteral(expression.toString());
        }

        return resolved;
    }

    private String encodeLiteral(String value) {
        return this.encodeLiteral() ? UriUtils.encode(value, this.charset, true) : value;
    }

    public List<String> getVariables() {
        return (List)this.templateChunks.stream().filter((templateChunk) -> {
            return Expression.class.isAssignableFrom(templateChunk.getClass());
        }).map((templateChunk) -> {
            return ((Expression)templateChunk).getName();
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }

    public List<String> getLiterals() {
        return (List)this.templateChunks.stream().filter((templateChunk) -> {
            return Literal.class.isAssignableFrom(templateChunk.getClass());
        }).map(Object::toString).filter(Objects::nonNull).collect(Collectors.toList());
    }

    public List<TemplateChunk> getTemplateChunks() {
        return Collections.unmodifiableList(this.templateChunks);
    }

    public boolean isLiteral() {
        return this.getVariables().isEmpty();
    }

    private void parseTemplate() {
        this.parseFragment(this.template);
    }

    private void parseFragment(String fragment) {
        Template.ChunkTokenizer tokenizer = new Template.ChunkTokenizer(fragment);

        while(tokenizer.hasNext()) {
            String chunk = tokenizer.next();
            if (chunk.startsWith("{")) {
                Expression expression = Expressions.create(chunk);
                if (expression == null) {
                    this.templateChunks.add(Literal.create(this.encodeLiteral(chunk)));
                } else {
                    this.templateChunks.add(expression);
                }
            } else {
                this.templateChunks.add(Literal.create(this.encodeLiteral(chunk)));
            }
        }

    }

    @Override
    public String toString() {
        return (String)this.templateChunks.stream().map(TemplateChunk::getValue).collect(Collectors.joining());
    }

    public boolean encodeLiteral() {
        return this.encode.isEncodingRequired();
    }

    boolean encodeSlash() {
        return this.encodeSlash;
    }

    public Charset getCharset() {
        return this.charset;
    }

    public static enum ExpansionOptions {
        ALLOW_UNRESOLVED,
        REQUIRED;

        private ExpansionOptions() {
        }
    }

    public static enum EncodingOptions {
        REQUIRED(true),
        NOT_REQUIRED(false);

        private boolean shouldEncode;

        private EncodingOptions(boolean shouldEncode) {
            this.shouldEncode = shouldEncode;
        }

        public boolean isEncodingRequired() {
            return this.shouldEncode;
        }
    }

    static class ChunkTokenizer {
        private List<String> tokens = new ArrayList();
        private int index;

        ChunkTokenizer(String template) {
            boolean outside = true;
            int level = 0;
            int lastIndex = 0;

            int idx;
            for(idx = 0; idx < template.length(); ++idx) {
                if (template.charAt(idx) == '{') {
                    if (outside) {
                        if (lastIndex < idx) {
                            this.tokens.add(template.substring(lastIndex, idx));
                        }

                        lastIndex = idx;
                        outside = false;
                    } else {
                        ++level;
                    }
                } else if (template.charAt(idx) == '}' && !outside) {
                    if (level > 0) {
                        --level;
                    } else {
                        if (lastIndex < idx) {
                            this.tokens.add(template.substring(lastIndex, idx + 1));
                        }

                        lastIndex = idx + 1;
                        outside = true;
                    }
                }
            }

            if (lastIndex < idx) {
                this.tokens.add(template.substring(lastIndex, idx));
            }

        }

        public boolean hasNext() {
            return this.tokens.size() > this.index;
        }

        public String next() {
            if (this.hasNext()) {
                return (String)this.tokens.get(this.index++);
            } else {
                throw new IllegalStateException("No More Elements");
            }
        }
    }
}
